1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, 9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 // See the License for the specific language governing permissions and 11 // limitations under the License. 12 13 package org.apache.tapestry5.ioc.internal.util; 14 15 import org.apache.tapestry5.ioc.Invokable; 16 17 import java.util.concurrent.TimeUnit; 18 import java.util.concurrent.locks.ReadWriteLock; 19 import java.util.concurrent.locks.ReentrantReadWriteLock; 20 21 /** 22 * A barrier used to execute code in a context where it is guarded by read/write locks. In addition, handles upgrading 23 * read locks to write locks (and vice versa). Execution of code within a lock is in terms of a {@link Runnable} object 24 * (that returns no value), or a {@link Invokable} object (which does return a value). 25 */ 26 public class ConcurrentBarrier 27 { 28 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 29 30 /** 31 * This is, of course, a bit of a problem. We don't have an avenue for ensuring that this ThreadLocal is destroyed 32 * at the end of the request, and that means a thread can hold a reference to the class and the class loader which 33 * loaded it. This may cause redeployment problems (leaked classes and class loaders). Apparently JDK 1.6 provides 34 * the APIs to check to see if the current thread has a read lock. So, we tend to remove the TL, rather than set its 35 * value to false. 36 */ 37 private static class ThreadBoolean extends ThreadLocal<Boolean> 38 { 39 @Override 40 protected Boolean initialValue() 41 { 42 return false; 43 } 44 } 45 46 private final ThreadBoolean threadHasReadLock = new ThreadBoolean(); 47 48 /** 49 * Invokes the object after acquiring the read lock (if necessary). If invoked when the read lock has not yet been 50 * acquired, then the lock is acquired for the duration of the call. If the lock has already been acquired, then the 51 * status of the lock is not changed. 52 * 53 * TODO: Check to see if the write lock is acquired and <em>not</em> acquire the read lock in that situation. 54 * Currently this code is not re-entrant. If a write lock is already acquired and the thread attempts to get the 55 * read lock, then the thread will hang. For the moment, all the uses of ConcurrentBarrier are coded in such a way 56 * that reentrant locks are not a problem. 57 * 58 * @param <T> 59 * @param invokable 60 * @return the result of invoking the invokable 61 */ 62 public <T> T withRead(Invokable<T> invokable) 63 { 64 boolean readLockedAtEntry; 65 66 synchronized (threadHasReadLock) 67 { 68 readLockedAtEntry = threadHasReadLock.get(); 69 } 70 71 if (!readLockedAtEntry) 72 { 73 lock.readLock().lock(); 74 75 synchronized (threadHasReadLock) 76 { 77 threadHasReadLock.set(true); 78 } 79 } 80 81 try 82 { 83 return invokable.invoke(); 84 } 85 finally 86 { 87 if (!readLockedAtEntry) 88 { 89 lock.readLock().unlock(); 90 91 synchronized (threadHasReadLock) 92 { 93 threadHasReadLock.remove(); 94 } 95 } 96 } 97 } 98 99 /** 100 * As with {@link #withRead(Invokable)}, creating an {@link Invokable} wrapper around the runnable object. 101 */ 102 public void withRead(final Runnable runnable) 103 { 104 Invokable<Void> invokable = new Invokable<Void>() 105 { 106 @Override 107 public Void invoke() 108 { 109 runnable.run(); 110 111 return null; 112 } 113 }; 114 115 withRead(invokable); 116 } 117 118 /** 119 * Acquires the exclusive write lock before invoking the Invokable. The code will be executed exclusively, no other 120 * reader or writer threads will exist (they will be blocked waiting for the lock). If the current thread has a read 121 * lock, it is released before attempting to acquire the write lock, and re-acquired after the write lock is 122 * released. Note that in that short window, between releasing the read lock and acquiring the write lock, it is 123 * entirely possible that some other thread will sneak in and do some work, so the {@link Invokable} object should 124 * be prepared for cases where the state has changed slightly, despite holding the read lock. This usually manifests 125 * as race conditions where either a) some parallel unrelated bit of work has occured or b) duplicate work has 126 * occured. The latter is only problematic if the operation is very expensive. 127 * 128 * @param <T> 129 * @param invokable 130 */ 131 public <T> T withWrite(Invokable<T> invokable) 132 { 133 boolean readLockedAtEntry = releaseReadLock(); 134 135 lock.writeLock().lock(); 136 137 try 138 { 139 return invokable.invoke(); 140 } 141 finally 142 { 143 lock.writeLock().unlock(); 144 restoreReadLock(readLockedAtEntry); 145 } 146 } 147 148 private boolean releaseReadLock() 149 { 150 boolean readLockedAtEntry; 151 152 synchronized (threadHasReadLock) 153 { 154 readLockedAtEntry = threadHasReadLock.get(); 155 } 156 157 if (readLockedAtEntry) 158 { 159 lock.readLock().unlock(); 160 161 synchronized (threadHasReadLock) 162 { 163 threadHasReadLock.set(false); 164 } 165 } 166 167 return readLockedAtEntry; 168 } 169 170 private void restoreReadLock(boolean readLockedAtEntry) 171 { 172 if (readLockedAtEntry) 173 { 174 lock.readLock().lock(); 175 176 synchronized (threadHasReadLock) 177 { 178 threadHasReadLock.set(true); 179 } 180 } 181 else 182 { 183 synchronized (threadHasReadLock) 184 { 185 threadHasReadLock.remove(); 186 } 187 } 188 } 189 190 /** 191 * As with {@link #withWrite(Invokable)}, creating an {@link Invokable} wrapper around the runnable object. 192 */ 193 public void withWrite(final Runnable runnable) 194 { 195 Invokable<Void> invokable = new Invokable<Void>() 196 { 197 @Override 198 public Void invoke() 199 { 200 runnable.run(); 201 202 return null; 203 } 204 }; 205 206 withWrite(invokable); 207 } 208 209 /** 210 * Try to aquire the exclusive write lock and invoke the Runnable. If the write lock is obtained within the specfied 211 * timeout, then this method behaves as {@link #withWrite(Runnable)} and will return true. If the write lock is not 212 * obtained within the timeout then the runnable is never invoked and the method will return false. 213 * 214 * @param runnable Runnable object to execute inside the write lock. 215 * @param timeout Time to wait for write lock. 216 * @param timeoutUnit Units of timeout. 217 * @return true if lock was obtained and the runnable executed, or false otherwise. 218 */ 219 public boolean tryWithWrite(final Runnable runnable, long timeout, TimeUnit timeoutUnit) 220 { 221 boolean readLockedAtEntry = releaseReadLock(); 222 223 boolean obtainedLock = false; 224 225 try 226 { 227 try 228 { 229 obtainedLock = lock.writeLock().tryLock(timeout, timeoutUnit); 230 231 if (obtainedLock) runnable.run(); 232 233 } 234 catch (InterruptedException e) 235 { 236 obtainedLock = false; 237 } 238 finally 239 { 240 if (obtainedLock) lock.writeLock().unlock(); 241 } 242 } 243 finally 244 { 245 restoreReadLock(readLockedAtEntry); 246 } 247 248 return obtainedLock; 249 } 250 251 }